package com.mzj.saas.commons.util;

import java.io.Serializable;


/* ------------------------------------------------------------ */
/**
 * Http URI. Parse a HTTP URI from a string or byte array. Given a URI
 * <code>http://user@host:port/path/info;param?query#fragment</code> this class
 * will split it into the following undecoded optional elements:
 * <ul>
 * <li>{@link #getScheme()} - http:</li>
 * <li>{@link #getAuthority()} - //name@host:port</li>
 * <li>{@link #getHost()} - host</li>
 * <li>{@link #getPort()} - port</li>
 * <li>{@link #getPath()} - /path/info</li>
 * <li>{@link #getParam()} - param</li>
 * <li>{@link #getQuery()} - query</li>
 * <li>{@link #getFragment()} - fragment</li>
 * </ul>
 * 
 */
public class HttpURI implements Serializable,Cloneable
{
	private static final long serialVersionUID = 1L;
	
	private static final byte[] __empty = {};
	private final static int START = 0, AUTH_OR_PATH = 1, SCHEME_OR_PATH = 2,
			AUTH = 4, IPV6 = 5, PORT = 6, PATH = 7, PARAM = 8, QUERY = 9,
			ASTERISK = 10;

	protected String _rawString;
	boolean _partial = false;
	byte[] _raw = __empty;
	int _scheme;
	int _authority;
	int _host;
	int _port;
	int _portValue;
	int _path;
	int _param;
	int _query;
	int _fragment;
	int _end;
	boolean _encoded = false;

	String scheme ;
	String authority ;
	String userInfo ;
	String host ;
	String path ;
	String completePath ;
	String query ;
	String fragment ;
	String param ;
	String pathAndParam ;
	
	transient Utf8StringBuilder _utf8b = new Utf8StringBuilder(64);

	protected HttpURI()
	{
	}

	public HttpURI(String raw)
	{
		_rawString = raw.trim();
		byte[] b = _rawString.getBytes();
		parse2(b, 0, b.length);
		
		this.scheme = _getScheme() ;
		this.authority = _getAuthority() ;
		this.userInfo = _getUserInfo() ;
		this.host = _getHost() ;
		this.path = _getPath() ;
		this.completePath = _getCompletePath() ;
		this.query = _getQuery() ;
		this.fragment = _getFragment() ;
		this.param = _getParam() ;
		this.pathAndParam = _getPathAndParam() ;
	}

	void parse2(byte[] raw, int offset, int length)
	{
		_encoded = false;
		_raw = raw;
		int i = offset;
		int e = offset + length;
		int state = START;
		int m = offset;
		_end = offset + length;
		_scheme = offset;
		_authority = offset;
		_host = offset;
		_port = offset;
		_portValue = 80;
		_path = offset;
		_param = _end;
		_query = _end;
		_fragment = _end;
		while (i < e)
		{
			char c = (char) (0xff & _raw[i]);
			int s = i++;

			state: switch (state)
			{
			case START:
			{
				m = s;
				switch (c)
				{
				case '/':
					state = AUTH_OR_PATH;
					break;
				case ';':
					_param = s;
					state = PARAM;
					break;
				case '?':
					_param = s;
					_query = s;
					state = QUERY;
					break;
				case '#':
					_param = s;
					_query = s;
					_fragment = s;
					break;
				case '*':
					_path = s;
					state = ASTERISK;
					break;
					
				default:
					if (Character.isLetterOrDigit(c))
						state = SCHEME_OR_PATH;
					else if( '.' == c )
						state = AUTH_OR_PATH;
					else
						throw new IllegalArgumentException(
								"!(SCHEME|PATH|AUTH):"
										+ ByteUtils.bytes2str(_raw, UriUtils.__CHARSET, offset,	length));
				}

				continue;
			}

			case AUTH_OR_PATH:
			{
				if ((_partial || _scheme != _authority) && c == '/')
				{
					_host = i;
					_port = _end;
					_path = _end;
					state = AUTH;
				}
				else if (c == ';' || c == '?' || c == '#')
				{
					i--;
					state = PATH;
				}
				else
				{
					_host = m;
					_port = m;
					state = PATH;
				}
				continue;
			}

			case SCHEME_OR_PATH:
			{
				// short cut for http and https
				if (length > 6 && c == 't')
				{
					if (_raw[offset + 3] == ':')
					{
						s = offset + 3;
						i = offset + 4;
						c = ':';
					}
					else if (_raw[offset + 4] == ':')
					{
						s = offset + 4;
						i = offset + 5;
						c = ':';
					}
					else if (_raw[offset + 5] == ':')
					{
						s = offset + 5;
						i = offset + 6;
						c = ':';
					}
				}

				switch (c)
				{
				case ':':
				{
					m = i++;
					_authority = m;
					_path = m;
					c = (char) (0xff & _raw[i]);
					if (c == '/')
						state = AUTH_OR_PATH;
					else
					{
						_host = m;
						_port = m;
						state = PATH;
					}
					break;
				}

				case '/':
				{
					state = PATH;
					break;
				}

				case ';':
				{
					_param = s;
					state = PARAM;
					break;
				}

				case '?':
				{
					_param = s;
					_query = s;
					state = QUERY;
					break;
				}

				case '#':
				{
					_param = s;
					_query = s;
					_fragment = s;
					break;
				}
				}
				continue;
			}

			case AUTH:
			{
				switch (c)
				{

				case '/':
				{
					m = s;
					_path = m;
					_port = _path;
					state = PATH;
					break;
				}
				case '@':
				{
					_host = i;
					break;
				}
				case ':':
				{
					_port = s;
					state = PORT;
					break;
				}
				case '[':
				{
					state = IPV6;
					break;
				}
				case '?':
				{
					m = s ;
					_port = s ;
					_path = s ;
					_param = s;
					_query = s;
					state = QUERY;
					break;
				}
				case '#':
				{
					m = s ;
					_port = s ;
					_path = s ;
					_query = s ;
					_param = s;
					_query = s;
					_fragment = s;
					break;
				}
				}
				continue;
			}

			case IPV6:
			{
				switch (c)
				{
				case '/':
				{
					throw new IllegalArgumentException("No closing ']' for "
							+ ByteUtils.bytes2str(_raw,UriUtils.__CHARSET, offset, length));
				}
				case ']':
				{
					state = AUTH;
					break;
				}
				}

				continue;
			}

			case PORT:
			{
				if (c == '/')
				{
					m = s;
					_path = m;
					if (_port <= _authority)
						_port = _path;
					state = PATH;
				}
				continue;
			}

			case PATH:
			{
				switch (c)
				{
				case ';':
				{
					_param = s;
					state = PARAM;
					break;
				}
				case '?':
				{
					_param = s;
					_query = s;
					state = QUERY;
					break;
				}
				case '#':
				{
					_param = s;
					_query = s;
					_fragment = s;
					break state;
				}
				case '%':
				{
					_encoded = true;
				}
				}
				continue;
			}

			case PARAM:
			{
				switch (c)
				{
				case '?':
				{
					_query = s;
					state = QUERY;
					break;
				}
				case '#':
				{
					_query = s;
					_fragment = s;
					break state;
				}
				}
				continue;
			}

			case QUERY:
			{
				if (c == '#')
				{
					_fragment = s;
					break state;
				}
				continue;
			}

			case ASTERISK:
			{
				throw new IllegalArgumentException("only '*'");
			}
			}
		}

		if (_port < _path)
			_portValue = UriUtils.parseInt(_raw, _port + 1, _path - _port - 1,10);
	}

	private String toUtf8String(int offset, int length)
	{
		_utf8b.reset();
		_utf8b.append(_raw, offset, length);
		return _utf8b.toString();
	}

	public String getScheme()
	{
		return this.scheme ;
	}
	
	private String _getScheme()
	{
		if (_scheme == _authority)
			return null;
		int l = _authority - _scheme;
		if (l == 5 && _raw[_scheme] == 'h' && _raw[_scheme + 1] == 't'
				&& _raw[_scheme + 2] == 't' && _raw[_scheme + 3] == 'p')
			return "http";
		if (l == 6 && _raw[_scheme] == 'h' && _raw[_scheme + 1] == 't'
				&& _raw[_scheme + 2] == 't' && _raw[_scheme + 3] == 'p'
				&& _raw[_scheme + 4] == 's')
			return "https";

		return toUtf8String(_scheme, _authority - _scheme - 1);
	}

	public String getAuthority()
	{
		return this.authority ;
	}

	private String _getAuthority()
	{
		if (_authority == _path)
			return null;
		return toUtf8String(_authority, _path - _authority);
	}

	public String getHost()
	{
		return this.host ;
	}
	
	private String _getHost()
	{
		if (_host == _port)
			return null;
		return toUtf8String(_host, _port - _host);
	}

	public int getPort()
	{
		return _portValue;
	}

	public String getPath()
	{
		return this.path ;
	}
	
	private String _getPath()
	{
		if (_path == _param)
			return null;
		return toUtf8String(_path, _param - _path);
	}

	public String getPathAndParam()
	{
		return this.pathAndParam ;
	}
	
	private String _getPathAndParam()
	{
		if (_path == _query)
			return null;
		return toUtf8String(_path, _query - _path);
	}

	public String getCompletePath()
	{
		return this.completePath ;
	}

	private String _getCompletePath()
	{
		if (_path == _end)
			return null;
		return toUtf8String(_path, _end - _path);
	}

	public String getParam()
	{
		return this.param ;
	}
	
	private String _getParam()
	{
		if (_param == _query)
			return null;
		return toUtf8String(_param + 1, _query - _param - 1);
	}
	
	public String getQuery()
	{
		return this.query ;
	}
	
	private String _getQuery()
	{
		if (_query == _fragment)
			return null;
		return toUtf8String(_query + 1, _fragment - _query - 1);
	}

	public String getFragment()
	{
		return this.fragment ;
	}
	
	private String _getFragment()
	{
		if (_fragment == _end)
			return null;
		return toUtf8String(_fragment + 1, _end - _fragment - 1);
	}

	public String getQuery(String encoding)
	{
		if (_query == _fragment)
			return null;
		return ByteUtils.bytes2str(_raw,encoding, _query + 1, _fragment - _query - 1);
	}
	
	public String getUserInfo()
	{
		return this.userInfo ;
	}
	
	private String _getUserInfo()
	{
		String temp = getAuthority() ;
		if( temp == null )
			return null ;
		int pos = temp.indexOf('@',2) ;
		if( pos <= 2 )
			return null ;
		else
			return temp.substring(2,pos) ;
	}

	public boolean hasQuery()
	{
		return (_fragment > _query);
	}

	@Override
	public String toString()
	{
		return _rawString;
	}

	@Override
	public boolean equals(Object obj)
	{
		if( obj == null || !this.getClass().isAssignableFrom( obj.getClass() ) )
			return false ;
		HttpURI uri = (HttpURI)obj ;
		return _rawString==null?uri._rawString==null:_rawString.equals(uri._rawString) ;
	}
	
	@Override
	public HttpURI clone()
	{
		try
		{
			HttpURI uri = (HttpURI)super.clone();
			if(this._raw==null)
				uri._raw = null ;
			else
			{
				uri._raw = new byte[this._raw.length] ;
				System.arraycopy( this._raw, 0, uri._raw, 0, uri._raw.length ) ;
			}
			
			return uri ;
		}
		catch( CloneNotSupportedException err )
		{
			throw new RuntimeException( err ) ;
		}
	}

	public int hashCode()
	{
		return _rawString==null?0:this._rawString.hashCode();
	}
	
	
	class Utf8StringBuilder
	{
	  StringBuilder _buffer;
	  int _more;
	  int _bits;

	  public Utf8StringBuilder()
	  {
	    this._buffer = new StringBuilder();
	  }

	  public Utf8StringBuilder(int capacity)
	  {
	    this._buffer = new StringBuilder(capacity);
	  }

	  public void append(byte[] b, int offset, int length)
	  {
	    int end = offset + length;
	    for (int i = offset; i < end; ++i)
	      append(b[i]);
	  }

	  public void append(byte b)
	  {
	    if (b >= 0)
	    {
	      if (this._more > 0)
	      {
	        this._buffer.append('?');
	        this._more = 0;
	        this._bits = 0;
	      }
	      else {
	        this._buffer.append((char)(0x7F & b));
	      }
	    } else if (this._more == 0)
	    {
	      if ((b & 0xC0) != 192)
	      {
	        this._buffer.append('?');
	        this._more = 0;
	        this._bits = 0;
	      }
	      else
	      {
	        if ((b & 0xE0) == 192)
	        {
	          this._more = 1;
	          this._bits = (b & 0x1F);
	        }
	        else if ((b & 0xF0) == 224)
	        {
	          this._more = 2;
	          this._bits = (b & 0xF);
	        }
	        else if ((b & 0xF8) == 240)
	        {
	          this._more = 3;
	          this._bits = (b & 0x7);
	        }
	        else if ((b & 0xFC) == 248)
	        {
	          this._more = 4;
	          this._bits = (b & 0x3);
	        }
	        else if ((b & 0xFE) == 252)
	        {
	          this._more = 5;
	          this._bits = (b & 0x1);
	        }
	        else
	        {
	          throw new IllegalArgumentException("!utf8");
	        }

	        if (this._bits == 0)
	          throw new IllegalArgumentException("!utf8");
	      }
	    }
	    else
	    {
	      if ((b & 0xC0) == 192)
	      {
	        this._buffer.append('?');
	        this._more = 0;
	        this._bits = 0;
	        throw new IllegalArgumentException("!utf8");
	      }

	      this._bits = (this._bits << 6 | b & 0x3F);
	      if (--this._more == 0)
	        this._buffer.append((char)this._bits);
	    }
	  }

	  public int length()
	  {
	    return this._buffer.length();
	  }

	  public void reset()
	  {
	    this._buffer.setLength(0);
	    this._more = 0;
	    this._bits = 0;
	  }

	  public StringBuilder getStringBuilder()
	  {
	    if (this._more != 0)
	      throw new IllegalStateException("!utf8");
	    return this._buffer;
	  }

	  public String toString()
	  {
	    if (this._more != 0)
	      throw new IllegalStateException("!utf8");
	    return this._buffer.toString();
	  }
	}	
}
